Mastering-Spark-SQL学习笔记02 SparkSession - Spark SQL的入口点

SparkSession是在使用类型化数据集(或基于非类型化Row-基于DataFrame)数据抽象开发Spark SQL应用程序时创建的首批对象之一。

在Spark 2.0中,SparkSession将SQLContext和HiveContext合并到一个对象中。

使用SparkSession.builder方法来创建一个SparkSession实例,使用stop方法停止SparkSession实例。

import org.apache.spark.sql.SparkSession
val spark: SparkSession = SparkSession.builder
  .appName("My Spark Application") // optional and will be auto generated if not specified
  .master("local[*]")   // only for demo and testing purposes, use spark-submit instead
  .enableHiveSupport()  // self-explanatory, isn't it?
  .config("spark.sql.warehouse.dir", "target/spark-warehouse")
  .getOrCreate
...
...
spark.stop // 停止当前的SparkSession的方法

一个Spark SQL应用中可以有多个SparkSession。通常的用例是在每个SparkSession的catalog中保持关系实体在逻辑上独立。

可以通过支持外部的Hive metastore来启用Hive支持(这在使用Apache Impala等其他大数据项目的项目中尤其有用)。

implicitis对象

implicits对象是一个具有Scala隐式方法(又名转换)的助手类,用于将Scala对象转换为Datasets、DataFrame和Columns对象。它还为Scala的“基本”类型定义了编码器(Encoder),例如Int、Double、String , 以及它们的Product和集合。 

val spark = SparkSession.builder.getOrCreate()
import spark.implicits._

implicits对象支持从任何类型(存在于Encoder范围内)的RDD创建数据集,或case类或元组,和Seq。implicits对象还提供从Scala Symbol或 $ 到 Column 的转换。

它也提供从RDD或Product类型(如case类或元组)的Seq到DataFrame的转换。它能直接把Int、Long、String的RDD转化为带有单个列名为"_1"的列的DataFrame。只允许对Int、Long、String等原始类型的RDD调用toDF方法

根据给定的Encoder创建空Dataset—— emptyDataset算子

emptyDataset 创建一个空 Dataset,类型为T。emptyDataset 创建了一个 LocalRelation logical query plan 。

emptyDataset[T: Encoder]: Dataset[T]

scala> val strings = spark.emptyDataset[String]
strings: org.apache.spark.sql.Dataset[String] = [value: string]
scala> strings.printSchema
root
|-- value: string (nullable = true)

从本地Collection或RDD创建Dataset —— createDataset方法

createDataset是一个实验性的API,用于从本地Scala集合(即Seq[T]、Java List[T]或分布式RDD[T])创建Dataset。

createDataset[T : Encoder](data: Seq[T]): Dataset[T]
createDataset[T : Encoder](data: RDD[T]): Dataset[T]

scala> val one = spark.createDataset(Seq(1))
one: org.apache.spark.sql.Dataset[Int] = [value: int]
scala> one.show
+-----+
|value|
+-----+
| 1|
+-----+

createDataset 创建一个 LocalRelation(为输入的集合数据)或 LogicalRDD(为输入的 RDD[T])。

提示:最好使用 scala implicits 和 toDS方法,而不是creatDataset创建数据集,这样就会自动进行类型转换

val spark: SparkSession = ...
import spark.implicits._
scala> val one = Seq(1).toDS
one: org.apache.spark.sql.Dataset[Int] = [value: int]

在内部,createDataset首先在范围内查找隐式表达式Encoder以访问schema的AttributeReference。目前只支持未解决的表达式Encoder。

表达式编码器被用来将(输入的Seq[T]的)元素映射到 InternalRows 集合中。 有了引用和行,createDataset返回一个带有LocalRelation逻辑查询计划的Dataset。

创建单长列的Dataset——range算子

range(end: Long): Dataset[java.lang.Long]
range(start: Long, end: Long): Dataset[java.lang.Long]
range(start: Long, end: Long, step: Long): Dataset[java.lang.Long]
range(start: Long, end: Long, step: Long, numPartitions: Int): Dataset[java.lang.Long]

range方法族创建Long类型数值的Dataset

scala> spark.range(start = 0, end = 4, step = 2, numPartitions = 5).show
+---+
| id|
+---+
| 0|
| 2|
+---+

在内部,range 创建了一个新的带有logical plan和Encoder.LONG encoder 的 Dataset[Long]。

创建空的DataFrame—— emptyDataFrame方法

emptyDataFrame: DataFrame

它调用了 createDataFrame方法,传入空的RDD[Row]和空schema的StructType(Nil)。它创建了一个没有行和列的空DataFrame

通过局部Collections或RDD来创建DataFrame——createDataFrame方法

createDataFrame[A <: Product : TypeTag](rdd: RDD[A]): DataFrame
createDataFrame[A <: Product : TypeTag](data: Seq[A]): DataFrame
createDataFrame(rowRDD: RDD[Row], schema: StructType): DataFrame
// private[sql]
createDataFrame(rowRDD: RDD[Row], schema: StructType, needsConversion: Boolean): DataFrame

它使用RDD[Row]和输入的schema来创建DataFrame,假定rowRDD中的行都匹配这个schema。

执行SQL查询(SQL模式)——sql方法

sql(sqlText: String): DataFrame

sql方法执行sqlText这个SQL语句块,并创建DataFrame。

sql在spark-shell中已经被import了,可以直接使用。

scala> sql("SHOW TABLES").show
+---------+-----------+
|tableName|isTemporary|
+---------+-----------+
| testdata| false|
+---------+-----------+

在内部,sql 请求当前的 ParserInterface去执行一个SQL查询,给出LogicalPlan。sql使用 SessionState访问当前的 ParserInterface。

然后,sql使用当前的 SparkSession和这个LogicalPlan创建 DataFrame。

访问UDF注册接口—— udf 属性

udf: UDFRegistration

udf 属性可以用来访问UDFRegistration,UDFRegistration允许为基于SQL的查询注册user-defined function(用户自定义函数)。

val spark: SparkSession = ...
spark.udf.register("myUpper", (s: String) => s.toUpperCase)
val strs = ('a' to 'c').map(_.toString).toDS
strs.registerTempTable("strs")
scala> sql("SELECT *, myUpper(value) UPPER FROM strs").show
+-----+-----+
|value|UPPER|
+-----+-----+
| a| A|
| b| B|
| c| C|
+-----+-----+

在内部,udf 仅仅是SessionState.udfRegistration的别名。

// In org.apache.spark.sql.SparkSession.scala
def udf: UDFRegistration = sessionState.udfRegistration

为表创建DataFrame(把表加载成DataFrame)—— table 方法

table(tableName: String): DataFrame (1)
table(tableIdent: TableIdentifier): DataFrame // private[sql]

方法1 把 tableName 解析成为一个 TableIdentifier,然后调用另一个table 方法。

table方法把输入的表tableName(仅在session catalog会话目录中可用)封装成一个DataFrame

scala> spark.version
res0: String = 2.3.0
scala> spark.catalog.tableExists("t1")
res1: Boolean = true
// t1 exists in the catalog
// let's load it
val t1 = spark.table("t1")

访问元数据存储—— catalog 属性

catalog: Catalog

catalog 属性是一个惰性接口,它可以访问当前元数据存储,例如(关系型实体如数据库、表、函数、表的列和视图)的 data catalog。

通过catalog,用户可以create、drop、alter或query数据库、表、函数。

scala> spark.catalog.listTables.show
+------------------+--------+-----------+---------+-----------+
| name|database|description|tableType|isTemporary|
+------------------+--------+-----------+---------+-----------+
|my_permanent_table| default| null| MANAGED| false|
| strs| null| null|TEMPORARY| true|
+------------------+--------+-----------+---------+-----------+

在内部,catalog创建了一个使用当前SparkSession的CatalogImpl对象

@transient lazy val catalog: Catalog = new CatalogImpl(self)

访问 DataFrameReader —— read 方法

read 方法返回一个 DataFrameReader,DataFrameReader用来从外部存储系统读取数据并把数据加载成一个 DataFrame

read: DataFrameReader

val spark: SparkSession = // create instance
val dfReader: DataFrameReader = spark.read

运行时配置 —— conf 属性

@transient lazy val conf: RuntimeConfig = new RuntimeConfig(sessionState.conf)

conf 返回当前运行时的配置(作为一个RuntimeConfig对象),这个配置封装了 SQLConf。

readStream 方法返回一个 DataStreamReader

streams 属性

def stream: StreamingQueryManager = sessionState.streamingQueryManager

streams 属性通过 SessionState访问StreamingQueryManager,返回的StreamingQueryManager可以激活所有的StreamingQuery

val spark: SparkSession = ...
spark.streams.active.foreach(println)

experimentalMethods 属性

def experimental: ExperimentalMethods = sessionState.experimentalMethods

experimentalMethods是一个带有 ExperimentalMethods 的扩展点,它是每个会话中额外策略和Rule[LogicalPlan]的集合。

experimental 在 SparkPlanner和 SparkOptimizer中被使用。Hive和Structured Streaming将其用于自己的额外策略和优化规则。

newSession 方法

// Start a new session with isolated SQL configurations, temporary tables, registered  functions are isolated, 
// but sharing the underlying `SparkContext` and cached data.
def newSession(): SparkSession = {
    new SparkSession(sparkContext, Some(sharedState), parentSessionState = None, extensions)
}

newSession 使用当前的SparkContext和SharedState 创建(启动)一个新的 SparkSession。

scala> println(sc.version)
2.3.0
scala> val newSession = spark.newSession
newSession: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@122f58a

关停 SparkSession —— stop 方法

  /**
   * Stop the underlying `SparkContext`.
   *
   * @since 2.0.0
   */
  def stop(): Unit = {
    sparkContext.stop()
  }

从 BaseRelation 创建DataFrame —— baseRelationToDataFrame 方法

/**
   * Convert a `BaseRelation` created for external data sources into a `DataFrame`.
   *
   * @since 2.0.0
   */
  def baseRelationToDataFrame(baseRelation: BaseRelation): DataFrame = {
    Dataset.ofRows(self, LogicalRelation(baseRelation))
  }

此方法将输入的BaseRelation对象封装到LogicalRelation内部,并通过这个LogicalRelation对象创建 DataFrame。

LogicalRelation是 BaseRelation 的一个逻辑计划适配器,所以 BaseRelation可以是一个逻辑计划的一部分。

baseRelationToDataFrame被使用的场景:

1. DataFrameReader:从一个支持多路径的数据源中加载数据

2. DataFrameReader:使用jdbc从一个外部表中加载数据

3. TextInputCSVDataSource:创建一个String的 Dataset

4. TextInputJsonDataSource:创建一个String的 Dataset

构造 SessionState ——  instantiateSessionState 内部方法(私有方法)

/**
   * Helper method to create an instance of `SessionState` based on `className` from conf.
   * The result is either `SessionState` or a Hive based `SessionState`.
   */
  private def instantiateSessionState(
      className: String,
      sparkSession: SparkSession): SessionState = {...}

instantiateSessionState 利用 className 创建和构造一个BaseSessionStateBuilder,初始化SessionState类时可能会抛出IllegalArgumentException。

sessionStateClassName 内部方法

private def sessionStateClassName(conf: SparkConf): String = {
    conf.get(CATALOG_IMPLEMENTATION) match {
      case "hive" => HIVE_SESSION_STATE_BUILDER_CLASS_NAME
      case "in-memory" => classOf[SessionStateBuilder].getCanonicalName
    }
  }

对于每个spark.sql.catalogImplementation,sessionStateClassName都给出了SessionState的类名:

hive -  org.apache.spark.sql.hive.HiveSessionStateBuilder

in-memory - org.apache.spark.sql.internal.SessionStateBuilder

从内部二进制行的RDD创建DataFrame —— internalCreateDataFrame 内部方法

  /**
   * Creates a `DataFrame` from an `RDD[InternalRow]`.
   */
  private[sql] def internalCreateDataFrame(
      catalystRows: RDD[InternalRow],
      schema: StructType,
      isStreaming: Boolean = false): DataFrame = {
    // TODO: use MutableProjection when rowRDD is another DataFrame and the applied
    // schema differs from the existing schema on any field data type.
    val logicalPlan = LogicalRDD(
      schema.toAttributes,
      catalystRows,
      isStreaming = isStreaming)(self)
    Dataset.ofRows(self, logicalPlan)
  }

此方法使用 LogicalRDD创建DataFrame。使用情景为:

1. DataFrameReader 被请求以JSON或CSV的Dataset创建一个DataFrame

2. SparkSession 被请求 以行的RDD创建一个DataFrame

3. InsertIntoDataSourceCommand 被执行

posted @ 2018-08-15 18:55  子秦  阅读(724)  评论(0编辑  收藏  举报